package com.cjm.one.security.fillter;

import cn.hutool.jwt.JWT;
import com.cjm.one.common.constant.RedisConstants;
import com.cjm.one.common.constant.SecurityConstants;
import com.cjm.one.common.constant.TokenConstants;
import com.cjm.one.common.result.Result;
import com.cjm.one.common.result.ResultCodeEnum;
import com.cjm.one.common.utils.IpUtils;
import com.cjm.one.common.utils.JSONUtils;
import com.cjm.one.common.utils.ResponseUtil;
import com.cjm.one.custom.client.CustomerInfoClient;
import com.cjm.one.custom.client.CustomerLoginLogClient;
import com.cjm.one.model.custom.entity.CustomerInfo;
import com.cjm.one.model.custom.entity.CustomerLoginLog;
import com.cjm.one.model.system.entity.SysUserInfo;
import com.cjm.one.model.system.vo.LoginVo;
import com.cjm.one.security.authenticationToken.EmailCodeAuthenticationToken;
import com.cjm.one.security.authenticationToken.EmailPasswordAuthenticationToken;
import com.cjm.one.security.authenticationToken.MobileCodeAuthenticationToken;
import com.cjm.one.security.custom.AppCustomer;
import com.cjm.one.security.custom.CustomUser;
import com.cjm.one.security.excetion.LoginException;
import com.cjm.one.system.client.SyStemUserClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
public class AppLoginFilter extends UsernamePasswordAuthenticationFilter {
    private RedisTemplate redisTemplate;

    private CustomerLoginLogClient customerLoginLogClient;
    private CustomerInfoClient customerInfoClient;

    public AppLoginFilter(AuthenticationProvider emailCodeAuthenticationProvider,
                          AuthenticationProvider mobileCodeAuthenticationProvider,
                          AuthenticationProvider emailPasswordAuthenticationProvider,
                          RedisTemplate redisTemplate,
                          CustomerInfoClient customerInfoClient,
                          CustomerLoginLogClient customerLoginLogClient) {
        List<AuthenticationProvider> providers = Arrays.asList(
                emailCodeAuthenticationProvider,
                mobileCodeAuthenticationProvider,
                emailPasswordAuthenticationProvider

        );
        this.setAuthenticationManager(new ProviderManager(providers));
        this.setPostOnly(false);
        //指定登录接口及提交方式，可以指定任意路径
        this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/app/customer/login","POST"));
        this.redisTemplate = redisTemplate;
        this.customerInfoClient = customerInfoClient;
        this.customerLoginLogClient = customerLoginLogClient;
    }
    /**
     * 登录认证
     * @param req
     * @param res
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException {
        try {
            LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);


            // 判断登录方式并构建认证凭证
            Authentication authenticationToken;
            if (isEmailCodeLogin(loginVo)) {
                authenticationToken = buildEmailCodeAuthentication(loginVo);
            } else if (isEmailPasswordLogin(loginVo)) {
                authenticationToken = buildEmailPasswordAuthentication(loginVo);
            } else if (isMobileCodeLogin(loginVo)) {
                authenticationToken = buildMobileCodeAuthentication(loginVo);
            } else if (isMobilePasswordLogin(loginVo)) {
                authenticationToken = buildMobilePasswordAuthentication(loginVo);
            } else {
                throw new LoginException(ResultCodeEnum.LOGIN_METHOD_INVALID);
            }

            log.info("登录认证方式: {}", authenticationToken.getClass().getSimpleName());
            return this.getAuthenticationManager().authenticate(authenticationToken);
        } catch (IOException e) {
            throw new RuntimeException("请求解析失败", e);
        }


    }

    /**
     * 登录成功
     * @param request
     * @param response
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        AppCustomer customUser = (AppCustomer) auth.getPrincipal();
        log.info("登录成功customUser:{}",customUser);
        CustomerInfo customerInfo = customUser.getCustomerInfo();
        log.info("登录成功customerInfo:{}",customerInfo);
        String token =  JWT.create()
                .setSigner(TokenConstants.SIGNER)
                .setPayload(TokenConstants.APP_USER, customerInfo)
                .sign();
        log.info("生成的token:{}",token);
        //将用户信息放入到redis
        redisTemplate.opsForValue().set(RedisConstants.APP_USER_LOGIN_KEY_TOKEN + token,
                JSONUtils.toString(customerInfo) ,
                TokenConstants.TOKEN_EXPIRATION_TIME,
                TimeUnit.SECONDS);
        Object o = redisTemplate.opsForValue().get(RedisConstants.APP_USER_LOGIN_KEY_TOKEN + token);
        log.info("redis中存储的key:{}",RedisConstants.APP_USER_LOGIN_KEY_TOKEN + token);
        log.info("redis中存储的customerInfo:{}",o);
        //保存权限数据
        //redisTemplate.boundHashOps("admin:auth").put(customUser.getUsername(), customUser.getAuthorities());

        //记录日志
        CustomerLoginLog customerLoginLog = new CustomerLoginLog();
        customerLoginLog.setCustomId(customerInfo.getId());
        customerLoginLog.setCreateTime(LocalDateTime.now());
        customerLoginLog.setIpaddr(IpUtils.getClientIp(request));
        customerLoginLog.setMsg("登录成功");
        Boolean data = customerLoginLogClient.add(customerLoginLog, SecurityConstants.INNER).getData();

        //customerLoginLogFeignClient.recordCustomerLoginLog(customerLoginLog);

//
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        ResponseUtil.out(response, Result.ok(map));
    }

    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) throws IOException, ServletException {

        if(e.getCause() instanceof RuntimeException) {
            ResponseUtil.out(response, Result.build(null, 204, e.getMessage()));
        } else {
            log.error("登录失败:{}", e.getMessage());
            ResponseUtil.out(response, Result.build(null, ResultCodeEnum.ACCOUNT_ERROR.getCode(),e.getMessage()));
        }
    }

    // 认证凭证构建方法
    private Authentication buildEmailCodeAuthentication(LoginVo loginVo) {
        // 这里需要实现邮箱验证码校验逻辑
        log.info("邮箱验证码登录");
        validateEmailCode(loginVo.getEmail(), loginVo.getSmsCode());
        return new EmailCodeAuthenticationToken(loginVo.getEmail(), loginVo.getSmsCode());
    }

    private Authentication buildEmailPasswordAuthentication(LoginVo loginVo) {
        return new EmailPasswordAuthenticationToken(
                loginVo.getEmail(),
                loginVo.getPassword()
        );
    }

    private Authentication buildMobileCodeAuthentication(LoginVo loginVo) {
        // 这里需要实现手机验证码校验逻辑
        validateMobileCode(loginVo.getMobile(), loginVo.getMobileCode());
        return new MobileCodeAuthenticationToken(loginVo.getMobile(), loginVo.getMobileCode());
    }

    private Authentication buildMobilePasswordAuthentication(LoginVo loginVo) {
        return new UsernamePasswordAuthenticationToken(
                loginVo.getMobile(),
                loginVo.getPassword()
        );
    }

    // 登录方式判断方法
    private boolean isEmailCodeLogin(LoginVo loginVo) {
        return StringUtils.hasText(loginVo.getEmail()) &&
                StringUtils.hasText(loginVo.getSmsCode()) &&
                !StringUtils.hasText(loginVo.getPassword()) &&
                !StringUtils.hasText(loginVo.getMobile());
    }

    private boolean isEmailPasswordLogin(LoginVo loginVo) {
        return StringUtils.hasText(loginVo.getEmail()) &&
                StringUtils.hasText(loginVo.getPassword()) &&
                !StringUtils.hasText(loginVo.getSmsCode()) &&
                !StringUtils.hasText(loginVo.getMobile());
    }

    private boolean isMobileCodeLogin(LoginVo loginVo) {
        return StringUtils.hasText(loginVo.getMobile()) &&
                StringUtils.hasText(loginVo.getMobileCode()) &&
                !StringUtils.hasText(loginVo.getPassword()) &&
                !StringUtils.hasText(loginVo.getEmail());
    }

    private boolean isMobilePasswordLogin(LoginVo loginVo) {
        return StringUtils.hasText(loginVo.getMobile()) &&
                StringUtils.hasText(loginVo.getPassword()) &&
                !StringUtils.hasText(loginVo.getMobileCode()) &&
                !StringUtils.hasText(loginVo.getEmail());
    }

    private void validateEmailCode(String email, String code) {
        String redisKey = RedisConstants.CAPTCHA_SMSCODE_KEY_EXPIRE + email;
        String storedCode = (String) redisTemplate.opsForValue().get(redisKey);

        if (!StringUtils.hasText(storedCode)) {
            throw new LoginException(ResultCodeEnum.EMAIL_CODE_EXPIRED);
        }
        if (!storedCode.equals(code)) {
            throw new LoginException(ResultCodeEnum.EMAIL_CODE_MISMATCH);
        }
    }

    // 手机验证码校验示例
    private void validateMobileCode(String mobile, String code) {
        String redisKey = RedisConstants.MOBILE_CODE_KEY + mobile;
        String storedCode = (String) redisTemplate.opsForValue().get(redisKey);

        if (!StringUtils.hasText(storedCode)) {
            throw new LoginException(ResultCodeEnum.CHECK_CODE_NULL);
        }
        if (!storedCode.equals(code)) {
            throw new LoginException(ResultCodeEnum.MOBILE_CODE_MISMATCH);
        }
    }
}


